#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>

#include "tcp.h"
#include "finfo.h"
#include "strfix.h"
#include "fatal.h"
#include "share.h"

#define text printf

#define TON  "\033[1m"
#define TOFF "\033[0m"


#define getsec(_new, _old)      ((_new.tv_sec - _old.tv_sec) + (_new.tv_usec - _old.tv_usec) / 1000000.0)
#define getmsec(_new, _old)     ((_new.tv_sec - _old.tv_sec) * 1000 + (_new.tv_usec - _old.tv_usec) / 1000)
#define tvcopy(_dst, _src)      ((_dst.tv_sec = _src.tv_sec) , (_dst.tv_usec = _src.tv_usec))

#define getmb(_byte)            ((_byte) / 1024 / 1024)
#define getper(_count, _total)  (int)((_count) * 100 / (_total))


static uint8_t *share_buffer = NULL;
static int share_buffer_size = 0;

void send_req(int sock)
{
    int  ret;

    ret = sendall(sock, FFSHARE_REQUEST, strlen(FFSHARE_REQUEST));
    if (ret <= 0)
        fatal("send req: send sock failed (%d)", ret);
}

void recv_req(int sock)
{
    int  ret;
    char buf[128] = {0};

    ret = recvbuf(sock, buf, sizeof(buf));
    if (ret <= 0)
        fatal("recv req: recv sock failed (%d)", ret);

    if (strcmp(buf, FFSHARE_REQUEST) != 0)
        fatal("recv req: compare req failed");
}

/*
down: 0
name: data.3
size: 134217728
mode: 436 (664)
sha1: eecadc5d651814948ac885f6119adde63a42b364
*/

void send_info(int sock, char *file, int down)
{
    int     ret;
    char    buf[2048] = {0};
    finfo_t info = {0};

    get_fileinfo(file, &info);
    sprintf(info.down, "%d", down);

    sprintf(buf, "down: %s\n" "name: %s\n" "size: %s\n" "mode: %s\n" "sha1: %s\n" "\n", \
            info.down, info.name, info.size, info.mode, info.sha1);

    // show(&info);
    ret = sendall(sock, buf, strlen(buf));
    if (ret <= 0)
        fatal("send info: send sock (%d)", ret);
}

int recv_info(int sock, finfo_t *info)
{
    int  ret;
    char buf[2048] = {0};
    char *p = buf;

    ret = recvbuf(sock, buf, sizeof(buf));
    if (ret <= 0)
        fatal("recv info: recv sock (%d)", ret);

    p = strbtwn(p, "down: ", "\n", info->down);
    if (p == NULL)
        fatal("recv info: parse (down)");

    p = strbtwn(p, "name: ", "\n", info->name);
    if (p == NULL)
        fatal("recv info: parse (name)");

    p = strbtwn(p, "size: ", "\n", info->size);
    if (p == NULL)
        fatal("recv info: parse (size)");

    p = strbtwn(p, "mode: ", "\n", info->mode);
    if (p == NULL)
        fatal("recv info: parse (mode)");

    p = strbtwn(p, "sha1: ", "\n", info->sha1);
    if (p == NULL)
        fatal("recv info: parse (sha1)");

    // show(info);

    return atoi(info->down);
}

int send_data(int sock, const char *name)
{
    int ret;
    int fd;

    fd = open(name, O_RDONLY);
    if (fd < 0)
        fatal("send data: open file failed (%d)", fd);

    for (;;) {
        ret = read(fd, share_buffer, share_buffer_size);
        if (ret < 0)
            fatal("send data: read file failed (%d)", ret);
        if (ret == 0)
            goto done;

        ret = sendall(sock, share_buffer, ret);
        if (ret <= 0)
            fatal("send data: send sock failed (%d)", ret);
    }

done:
    close(fd);
    return 0;
}

int recv_data(int sock, finfo_t *info)
{
    int  ret;
    int  fd;
    int64_t info_size = 0;
    int64_t size = 0;
    int  mode;
    char name[128] = {0};
    char sha1[64] = {0};
    double ui = 0;
    int64_t old_size = 0;
    struct timeval start, blink, old, now;

    sprintf(name, "_%s.ff", info->sha1);

    ret = strint64(info->size, &info_size);
    if (ret < 0)
        fatal("recv data: gen size (%d)", ret);

    ret = strint(info->mode, &mode);
    if (ret < 0)
        fatal("recv data: gen mode (%d)", ret);

    // ui
    int ui_flag = 0;
    gettimeofday(&start, NULL);
    tvcopy(blink, start);
    text("\033[1m%s\033[0m \n", info->name);

    fd = open(name, O_CREAT | O_TRUNC | O_RDWR, (mode_t)mode);
    if (fd < 0)
        fatal("recv data: open (%d)", fd);


    old_size = 0;
    for (;;) {
        ret = recvbuf(sock, share_buffer, share_buffer_size);
        if (ret < 0)
            fatal("recv data: recv sock (%d)", ret);
        if (ret == 0) {
            break;
        }

        ret = write(fd, share_buffer, ret);
        if (ret < 0)
            fatal("recv data: write file (%d)", ret);

        size += ret;


        if (size >= info_size) {
            break;
        }

        // ui start
        gettimeofday(&now, NULL);
        if (getmsec(now, blink) < 500 && ui_flag != 0)
            continue;
        ui_flag = 1;

        text("\r-  fetch:  %d%%  %luMB  %.1fs  %.1fMB/s  ", getper(size, info_size), getmb(size), getsec(now, start), getmb(size - old_size) / getsec(now, blink));
        fflush(stdout);

        tvcopy(blink, now);
        old_size = size;
        //ui = total;
    }

    gettimeofday(&now, NULL);
    text("\r-  fetch:  %d%%  %luMB  %.1fs  %.1fMB/s  ", getper(size, info_size), getmb(size), getsec(now, start), getmb(size - old_size) / getsec(now, blink));
    text("\n");

    close(fd);

    // check
    if (size != info_size) {
        remove(name);
        fatal("recv data: wrong size (%lu/%lu)", size, info_size);
    }

    //gettimeofday(&past, NULL);
    ret = sha1ui(name, sha1);
    if (ret < 0) {
        remove(name);
        fatal("recv data: gen sha1 (%d)", ret);
    }
    gettimeofday(&now, NULL);
    //double sec   = (now.tv_sec - past.tv_sec) + (now.tv_usec - past.tv_usec) / 1000000.0;


    if (strcmp(sha1, info->sha1) != 0) {
        remove(name);
        fatal("recv data: wrong sha1 (%s)", sha1);
    }

    rename(name, info->name);
    return 0;
}

int share_init(int size)
{
    if (size <= 0)
        size = FFSHARE_BUFSIZE;

    if (share_buffer == NULL) {
        share_buffer = (uint8_t *)malloc(size);
        if (share_buffer == NULL)
            fatal("share: alloc buffer failed");
    }

    share_buffer_size = size;
    return 0;
}

int share(int sock, char *file_list[], int count)
{
    for (int i = 0; i < count; i++) {
        recv_req(sock);
        send_info(sock, file_list[i], count-i-1);
        recv_req(sock);
        send_data(sock, file_list[i]);
    }
    return 0;
}

int download(int sock)
{
    finfo_t info = {0};
    int down = 0;

    for (;;) {
        send_req(sock);
        down = recv_info(sock, &info);
        send_req(sock);
        recv_data(sock, &info);
        if (down == 0) {
            break;
        }
    }
    return 0;
}

